Completed
Pull Request — master (#328)
by
unknown
02:47
created

View.elementToFile   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
/**
2
 * Nextcloud - Gallery
3
 *
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 *
10
 * @copyright Olivier Paroz 2017
11
 */
12
/* global Handlebars, Gallery, GalleryImage, Thumbnails */
13
(function ($, _, OC, t, Gallery) {
14
	"use strict";
15
16
	var TEMPLATE_ADDBUTTON = '<a href="#" class="button new"><span class="icon icon-add"></span><span class="hidden-visually">New</span></a>';
0 ignored issues
show
Coding Style introduced by
Line is too long.
Loading history...
17
	var TEMPLATE_DOWNLOADBUTTON = '<span id="selectedActionsList" class="selectedActions hidden">' +
18
		'<a href="#" class="button download"><span class="icon icon-download"></span>' +
19
		'<span class="hidden-visually">Download</span></a></span>';
20
21
	/**
22
	 * Builds and updates the Gallery view
23
	 *
24
	 * @constructor
25
	 */
26
	var View = function () {
27
		this.element = $('#gallery');
28
		this.loadVisibleRows.loading = false;
29
		this._setupUploader();
30
		this.breadcrumb = new Gallery.Breadcrumb();
31
		this.emptyContentElement = $('#emptycontent');
32
		this.controlsElement = $('#controls');
33
	};
34
35
	View.prototype = {
36
		element: null,
37
		breadcrumb: null,
38
		requestId: -1,
39
		emptyContentElement: null,
40
		controlsElement: null,
41
		/**
42
		 * Map of file id to file data
43
		 * @type Object.<int, Object>
44
		 */
45
		_selectedFiles: {},
46
		/**
47
		 * Summary of selected files.
48
		 * @type OCA.Files.FileSummary
49
		 */
50
		_selectionSummary: null,
51
		/**
52
		 * @type Backbone.Model
53
		 */
54
		_filesConfig: undefined,
55
		/**
56
		 * Initialiation status
57
		 * @type Boolean
58
		 */
59
		_initialized: false,
60
61
		/**
62
		 * Removes all thumbnails from the view
63
		 */
64
		clear: function () {
65
			this.loadVisibleRows.processing = false;
66
			this.loadVisibleRows.loading = null;
67
			// We want to keep all the events
68
			this.element.children().detach();
69
			this.showLoading();
70
		},
71
72
		/**
73
		 * @param {string} path
74
		 * @returns {boolean}
75
		 */
76
		_isValidPath: function(path) {
77
			var sections = path.split('/');
78
			for (var i = 0; i < sections.length; i++) {
79
				if (sections[i] === '..') {
80
					return false;
81
				}
82
			}
83
84
			return path.toLowerCase().indexOf(decodeURI('%0a')) === -1 &&
85
				path.toLowerCase().indexOf(decodeURI('%00')) === -1;
86
		},
87
88
		/**
89
		 * Populates the view if there are images or albums to show
90
		 *
91
		 * @param {string} albumPath
92
		 * @param {string|undefined} errorMessage
93
		 */
94
		init: function (albumPath, errorMessage) {
95
			// Set path to an empty value if not a valid one
96
			if(!this._isValidPath(albumPath)) {
97
				albumPath = '';
98
			}
99
100
			// Only do it when the app is initialised
101
			if (this.requestId === -1) {
102
				this._initButtons();
103
				this._blankUrl();
104
			}
105
			if ($.isEmptyObject(Gallery.imageMap)) {
106
				Gallery.view.showEmptyFolder(albumPath, errorMessage);
107
			} else {
108
				this.viewAlbum(albumPath);
109
			}
110
111
			this._setBackgroundColour();
112
113
			this._initSelection();
114
			this.initialized = true;
115
		},
116
117
		/**
118
		 * Starts the slideshow
119
		 *
120
		 * @param {string} path
121
		 * @param {string} albumPath
122
		 */
123
		startSlideshow: function (path, albumPath) {
124
			var album = Gallery.albumMap[albumPath];
125
			var images = album.images;
126
			var startImage = Gallery.imageMap[path];
127
			Gallery.slideShow(images, startImage, false);
128
		},
129
130
		/**
131
		 * Sets up the controls and starts loading the gallery rows
132
		 *
133
		 * @param {string|null} albumPath
134
		 */
135
		viewAlbum: function (albumPath) {
136
			albumPath = albumPath || '';
137
			if (!Gallery.albumMap[albumPath]) {
138
				return;
139
			}
140
141
			this.clear();
142
143
			if (albumPath !== Gallery.currentAlbum
144
				|| (albumPath === Gallery.currentAlbum &&
145
				Gallery.albumMap[albumPath].etag !== Gallery.currentEtag)) {
146
				Gallery.currentAlbum = albumPath;
147
				Gallery.currentEtag = Gallery.albumMap[albumPath].etag;
148
				this._setupButtons(albumPath);
149
			}
150
151
			Gallery.albumMap[albumPath].viewedItems = 0;
152
			Gallery.albumMap[albumPath].preloadOffset = 0;
153
154
			// Each request has a unique ID, so that we can track which request a row belongs to
155
			this.requestId = Math.random();
156
			Gallery.albumMap[Gallery.currentAlbum].requestId = this.requestId;
157
158
			// Loading rows without blocking the execution of the rest of the script
159
			setTimeout(function () {
160
				this.loadVisibleRows.activeIndex = 0;
161
				this.loadVisibleRows(Gallery.albumMap[Gallery.currentAlbum]);
162
			}.bind(this), 0);
163
		},
164
165
		/**
166
		 * Manages the sorting interface
167
		 *
168
		 * @param {string} sortType name or date
169
		 * @param {string} sortOrder asc or des
170
		 */
171
		sortControlsSetup: function (sortType, sortOrder) {
172
			var reverseSortType = 'date';
173
			if (sortType === 'date') {
174
				reverseSortType = 'name';
175
			}
176
			this._setSortButton(sortType, sortOrder, true);
177
			this._setSortButton(reverseSortType, 'asc', false); // default icon
178
		},
179
180
		/**
181
		 * Loads and displays gallery rows on screen
182
		 *
183
		 * view.loadVisibleRows.loading holds the Promise of a row
184
		 *
185
		 * @param {Album} album
186
		 */
187
		loadVisibleRows: function (album) {
188
			var view = this;
189
			// Wait for the previous request to be completed
190
			if (this.loadVisibleRows.processing) {
191
				return;
192
			}
193
194
			/**
195
			 * At this stage, there is no loading taking place, so we can look for new rows
196
			 */
197
198
			var scroll = $('#content-wrapper').scrollTop() + $(window).scrollTop();
199
			// 2 windows worth of rows is the limit from which we need to start loading new rows.
200
			// As we scroll down, it grows
201
			var targetHeight = ($(window).height() * 2) + scroll;
202
			// We throttle rows in order to try and not generate too many CSS resizing events at
203
			// the same time
204
			var showRows = _.throttle(function (album) {
205
206
				// If we've reached the end of the album, we kill the loader
207
				if (!(album.viewedItems < album.subAlbums.length + album.images.length)) {
0 ignored issues
show
Coding Style introduced by
The usage of ! looks confusing here.

The following shows a case which JSHint considers confusing and its respective non-confusing counterpart:

! (str.indexOf(i) > -1) // Bad
str.indexOf(i) === -1 // Good
Loading history...
208
					view.loadVisibleRows.processing = false;
209
					view.loadVisibleRows.loading = null;
210
					return;
211
				}
212
213
				// Prevents creating rows which are no longer required. I.e when changing album
214
				if (view.requestId !== album.requestId) {
215
					return;
216
				}
217
218
				// We can now safely create a new row
219
				var row = album.getRow($(window).width());
220
				var rowDom = row.getDom();
221
				view.element.append(rowDom);
222
223
				return album.fillNextRow(row).then(function () {
224
					if (album.viewedItems < album.subAlbums.length + album.images.length &&
225
						view.element.height() < targetHeight) {
226
						return showRows(album);
227
					}
228
					// No more rows to load at the moment
229
					view.loadVisibleRows.processing = false;
230
					view.loadVisibleRows.loading = null;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
231
				}, function () {
232
					// Something went wrong, so kill the loader
233
					view.loadVisibleRows.processing = false;
234
					view.loadVisibleRows.loading = null;
235
				});
236
			}, 100);
237
			if (this.element.height() < targetHeight) {
238
				this._showNormal();
239
				this.loadVisibleRows.processing = true;
240
				album.requestId = view.requestId;
241
				this.loadVisibleRows.loading = showRows(album);
242
			}
243
		},
244
245
		/**
246
		 * Shows an empty gallery message
247
		 *
248
		 * @param {string} albumPath
249
		 * @param {string|null} errorMessage
250
		 */
251
		showEmptyFolder: function (albumPath, errorMessage) {
252
			var message = '<div class="icon-gallery"></div>';
253
			var uploadAllowed = true;
254
255
			this.element.children().detach();
256
			this.removeLoading();
257
258
			if (!_.isUndefined(errorMessage) && errorMessage !== null) {
259
				message += '<h2>' + t('gallery',
260
						'Album cannot be shown') + '</h2>';
261
				message += '<p>' + escapeHTML(errorMessage) + '</p>';
0 ignored issues
show
Bug introduced by
escapeHTML does not seem to be defined.
Loading history...
262
				uploadAllowed = false;
263
			} else {
264
				message += '<h2>' + t('gallery',
265
						'No media files found') + '</h2>';
266
				// We can't upload yet on the public side
267
				if (Gallery.token) {
268
					message += '<p>' + t('gallery',
269
							'Upload pictures in the Files app to display them here') + '</p>';
270
				} else {
271
					message += '<p>' + t('gallery',
272
							'Upload new files via drag and drop or by using the [+] button above') +
273
						'</p>';
274
				}
275
			}
276
			this.emptyContentElement.html(message);
277
			this.emptyContentElement.removeClass('hidden');
278
279
			this._hideButtons(uploadAllowed);
280
			Gallery.currentAlbum = albumPath;
281
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
282
			this.breadcrumb.init(albumPath, availableWidth);
283
			Gallery.config.albumDesign = null;
284
		},
285
286
		/**
287
		 * Dims the controls bar when retrieving new content. Matches the effect in Files
288
		 */
289
		dimControls: function () {
290
			// Use the existing mask if its already there
291
			var $mask = this.controlsElement.find('.mask');
292
			if ($mask.exists()) {
293
				return;
294
			}
295
			$mask = $('<div class="mask transparent"></div>');
296
			this.controlsElement.append($mask);
297
			$mask.removeClass('transparent');
298
		},
299
300
		/**
301
		 * Shows the infamous loading spinner
302
		 */
303
		showLoading: function () {
304
			this.emptyContentElement.addClass('hidden');
305
			this.controlsElement.removeClass('hidden');
306
			$('#content').addClass('icon-loading');
307
			this.dimControls();
308
		},
309
310
		/**
311
		 * Removes the spinner in the main area and restore normal visibility of the controls bar
312
		 */
313
		removeLoading: function () {
314
			$('#content').removeClass('icon-loading');
315
			this.controlsElement.find('.mask').remove();
316
		},
317
318
		/**
319
		 * Shows thumbnails
320
		 */
321
		_showNormal: function () {
322
			this.emptyContentElement.addClass('hidden');
323
			this.controlsElement.removeClass('hidden');
324
			this.removeLoading();
325
		},
326
327
		/**
328
		 * Sets up our custom handlers for folder uploading operations
329
		 *
330
		 * @see OC.Upload.init/file_upload_param.done()
331
		 *
332
		 * @private
333
		 */
334
		_setupUploader: function () {
335
			var $uploadEl = $('#file_upload_start');
336
			if (!$uploadEl.exists()) {
337
				return;
338
			}
339
			this._uploader = new OC.Uploader($uploadEl, {
340
				fileList: FileList,
0 ignored issues
show
Bug introduced by
The variable FileList seems to be never declared. If this is a global, consider adding a /** global: FileList */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
341
				dropZone: $('#content')
342
			});
343
			this._uploader.on('add', function (e, data) {
344
				data.targetDir = '/' + Gallery.currentAlbum;
345
			});
346
			this._uploader.on('done', function (e, upload) {
347
				var data = upload.data;
348
349
				// is that the last upload ?
350
				if (data.files[0] === data.originalFiles[data.originalFiles.length - 1]) {
351
					var fileList = data.originalFiles;
352
					//Ask for a refresh of the photowall
353
					Gallery.getFiles(Gallery.currentAlbum).done(function () {
354
						var fileId, path;
355
						// Removes the cached thumbnails of files which have been re-uploaded
356
						_(fileList).each(function (fileName) {
357
							path = Gallery.currentAlbum + '/' + fileName;
358
							if (Gallery.imageMap[path]) {
359
								fileId = Gallery.imageMap[path].fileId;
360
								if (Thumbnails.map[fileId]) {
361
									delete Thumbnails.map[fileId];
362
								}
363
							}
364
						});
365
366
						Gallery.view.init(Gallery.currentAlbum);
367
					});
368
				}
369
			});
370
371
			// Since Nextcloud 9.0
372
			if (OC.Uploader) {
373
				OC.Uploader.prototype._isReceivedSharedFile = function (file) {
374
					var path = file.name;
375
					var sharedWith = false;
376
377
					if (Gallery.currentAlbum !== '' && Gallery.currentAlbum !== '/') {
378
						path = Gallery.currentAlbum + '/' + path;
379
					}
380
					if (Gallery.imageMap[path] && Gallery.imageMap[path].sharedWithUser) {
381
						sharedWith = true;
382
					}
383
384
					return sharedWith;
385
				};
386
			}
387
		},
388
389
		/**
390
		 * Setups selection feature
391
		 * Also resets file actions list to be hidden in case of user navigating
392
		 * back and forth between albums
393
		 *
394
		 * @private
395
		 */
396
		_initSelection: function() {
397
			this._selectedFiles = {};
398
			this._filesConfig = new OC.Backbone.Model();
399
			this._selectionSummary = new OCA.Files.FileSummary(undefined, {config: this._filesConfig});
400
			if (!this.initialized) {
401
				this.element.on('change', '.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
402
			}
403
404
			var selectedActionsList = $('#selectedActionsList');
405
			if (selectedActionsList && !selectedActionsList.hasClass('hidden')) {
406
				selectedActionsList.addClass('hidden');
407
			}
408
		},
409
410
		/**
411
		 * Adds all the click handlers to buttons the first time they appear in the interface
412
		 *
413
		 * @private
414
		 */
415
		_initButtons: function () {
416
			this.element.on("contextmenu", function(e) { e.preventDefault(); });
417
			$('#filelist-button').click(Gallery.switchToFilesView);
418
			$('#download').click(Gallery.download);
419
			$('#shared-button').click(Gallery.share);
420
			Gallery.infoBox = new Gallery.InfoBox();
421
			$('#album-info-button').click(Gallery.showInfo);
422
			$('#sort-name-button').click(Gallery.sorter);
423
			$('#sort-date-button').click(Gallery.sorter);
424
			$('#save #save-button').click(Gallery.showSaveForm);
425
			$('.save-form').submit(Gallery.saveForm);
426
			this._renderDownloadSelectedButton();
427
			this._renderNewButton();
428
			// Trigger cancelling of file upload
429
			$('#uploadprogresswrapper .stop').on('click', function () {
430
				OC.Upload.cancelUploads();
431
			});
432
			this.requestId = Math.random();
433
		},
434
435
		/**
436
		 * Sets up all the buttons of the interface and the breadcrumbs
437
		 *
438
		 * @param {string} albumPath
439
		 * @private
440
		 */
441
		_setupButtons: function (albumPath) {
442
			this._shareButtonSetup(albumPath);
443
			this._infoButtonSetup();
444
445
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
446
			this.breadcrumb.init(albumPath, availableWidth);
447
			var album = Gallery.albumMap[albumPath];
448
449
			var sum = album.images.length + album.subAlbums.length;
450
			//If sum of the number of images and subalbums exceeds 1 then show the buttons.
451
			if(sum > 1)
452
			{
453
				$('#sort-name-button').show();
454
				$('#sort-date-button').show();
455
			}
456
			else
457
			{
458
				$('#sort-name-button').hide();
459
				$('#sort-date-button').hide();
460
			}
461
			var currentSort = Gallery.config.albumSorting;
462
			this.sortControlsSetup(currentSort.type, currentSort.order);
463
			Gallery.albumMap[Gallery.currentAlbum].images.sort(
464
				Gallery.utility.sortBy(currentSort.type,
465
					currentSort.order));
466
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(Gallery.utility.sortBy('name',
467
				currentSort.albumOrder));
468
469
			$('#save-button').show();
470
			$('#download').show();
471
			$('a.button.new').show();
472
		},
473
474
		/**
475
		 * Hide buttons in the controls bar
476
		 *
477
		 * @param uploadAllowed
478
		 */
479
		_hideButtons: function (uploadAllowed) {
480
			$('#album-info-button').hide();
481
			$('#shared-button').hide();
482
			$('#sort-name-button').hide();
483
			$('#sort-date-button').hide();
484
			$('#save-button').hide();
485
			$('#download').hide();
486
487
			if (!uploadAllowed) {
488
				$('a.button.new').hide();
489
			}
490
		},
491
492
		/**
493
		 * Shows or hides the share button depending on if we're in a public gallery or not
494
		 *
495
		 * @param {string} albumPath
496
		 * @private
497
		 */
498
		_shareButtonSetup: function (albumPath) {
499
			var shareButton = $('#shared-button');
500
			if (albumPath === '' || Gallery.token) {
501
				shareButton.hide();
502
			} else {
503
				shareButton.show();
504
			}
505
		},
506
507
		/**
508
		 * Shows or hides the info button based on the information we've received from the server
509
		 *
510
		 * @private
511
		 */
512
		_infoButtonSetup: function () {
513
			var infoButton = $('#album-info-button');
514
			infoButton.find('span').hide();
515
			var infoContentContainer = $('.album-info-container');
516
			infoContentContainer.slideUp();
517
			infoContentContainer.css('max-height',
518
				$(window).height() - Gallery.browserToolbarHeight);
519
			var albumInfo = Gallery.config.albumInfo;
520
			if (Gallery.config.albumError) {
521
				infoButton.hide();
522
				var text = '<strong>' + t('gallery', 'Configuration error') + '</strong></br>' +
523
					Gallery.config.albumError.message + '</br></br>';
524
				Gallery.utility.showHtmlNotification(text, 7);
525
			} else if ($.isEmptyObject(albumInfo)) {
526
				infoButton.hide();
527
			} else {
528
				infoButton.show();
529
				if (albumInfo.inherit !== 'yes' || albumInfo.level === 0) {
530
					infoButton.find('span').delay(1000).slideDown();
531
				}
532
			}
533
		},
534
535
		/**
536
		 * Sets the background colour of the photowall
537
		 *
538
		 * @private
539
		 */
540
		_setBackgroundColour: function () {
541
			var wrapper = $('#content-wrapper');
542
			var albumDesign = Gallery.config.albumDesign;
543
			if (!$.isEmptyObject(albumDesign) && albumDesign.background) {
544
				wrapper.css('background-color', albumDesign.background);
545
			} else {
546
				wrapper.css('background-color', '#fff');
547
			}
548
		},
549
550
		/**
551
		 * Picks the image which matches the sort order
552
		 *
553
		 * @param {string} sortType name or date
554
		 * @param {string} sortOrder asc or des
555
		 * @param {boolean} active determines if we're setting up the active sort button
556
		 * @private
557
		 */
558
		_setSortButton: function (sortType, sortOrder, active) {
559
			var button = $('#sort-' + sortType + '-button');
560
			// Removing all the classes which control the image in the button
561
			button.removeClass('active');
562
			button.find('img').removeClass('front');
563
			button.find('img').removeClass('back');
564
565
			// We need to determine the reverse order in order to send that image to the back
566
			var reverseSortOrder = 'des';
567
			if (sortOrder === 'des') {
568
				reverseSortOrder = 'asc';
569
			}
570
571
			// We assign the proper order to the button images
572
			button.find('img.' + sortOrder).addClass('front');
573
			button.find('img.' + reverseSortOrder).addClass('back');
574
575
			// The active button needs a hover action for the flip effect
576
			if (active) {
577
				button.addClass('active');
578
				if (button.is(":hover")) {
579
					button.removeClass('hover');
580
				}
581
				// We can't use a toggle here
582
				button.hover(function () {
583
						$(this).addClass('hover');
584
					},
585
					function () {
586
						$(this).removeClass('hover');
587
					});
588
			}
589
		},
590
591
		/**
592
		 * If no url is entered then do not show the error box.
593
		 *
594
		 */
595
		_blankUrl: function() {
596
			$('#remote_address').on("change keyup paste", function() {
597
 				if ($(this).val() === '') {
598
 					$('#save-button-confirm').prop('disabled', true);
599
 				} else {
600
 					$('#save-button-confirm').prop('disabled', false);
601
 				}
602
			});
603
		},
604
605
		/**
606
		 * Creates the [+] button allowing users who can't drag and drop to upload files
607
		 *
608
		 * @see core/apps/files/js/filelist.js
609
		 * @private
610
		 */
611
		_renderNewButton: function () {
612
			// if no actions container exist, skip
613
			var $actionsContainer = $('.actions.creatable');
614
			if (!$actionsContainer.length) {
615
				return;
616
			}
617
			if (!this._addButtonTemplate) {
618
				this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON);
619
			}
620
			var $newButton = $(this._addButtonTemplate({
621
				addText: t('gallery', 'New'),
622
				iconUrl: OC.imagePath('core', 'actions/add')
623
			}));
624
625
			$actionsContainer.prepend($newButton);
626
			$newButton.tooltip({'placement': 'bottom'});
627
628
			$newButton.click(_.bind(this._onClickNewButton, this));
629
			this._newButton = $newButton;
630
		},
631
632
		/**
633
		 * Creates the click handler for the [+] button
634
		 * @param event
635
		 * @returns {boolean}
636
		 *
637
		 * @see core/apps/files/js/filelist.js
638
		 * @private
639
		 */
640
		_onClickNewButton: function (event) {
641
			var $target = $(event.target);
642
			if (!$target.hasClass('.button')) {
643
				$target = $target.closest('.button');
644
			}
645
			this._newButton.tooltip('hide');
646
			event.preventDefault();
647
			if ($target.hasClass('disabled')) {
648
				return false;
649
			}
650
			if (!this._newFileMenu) {
651
				this._newFileMenu = new Gallery.NewFileMenu();
652
				$('.actions').append(this._newFileMenu.$el);
653
			}
654
			this._newFileMenu.showAt($target);
655
656
			if (Gallery.currentAlbum === '') {
657
				$('.menuitem[data-action="hideAlbum"]').parent().hide();
658
			}
659
			return false;
660
		},
661
662
		/**
663
		 * Creates the download individual files button
664
		 *
665
		 * @see core/apps/files/js/filelist.js
666
		 * @private
667
		 */
668
		_renderDownloadSelectedButton: function () {
669
			var $actionsContainer = $('#controls .right');
670
			if (!this._downloadButtonTemplate) {
671
				this._downloadButtonTemplate = Handlebars.compile(TEMPLATE_DOWNLOADBUTTON);
672
			}
673
			var $downloadButton = $(this._downloadButtonTemplate({
674
				addText: t('gallery', 'Download'),
675
				iconUrl: OC.imagePath('core', 'actions/download')
676
			}));
677
678
			$actionsContainer.prepend($downloadButton);
679
			$downloadButton.tooltip({'placement': 'bottom'});
680
681
			$downloadButton.click(_.bind(this._onClickDownloadSelected, this));
682
		},
683
684
		/**
685
		 * Event handler for when clicking on "Download" for the selected files
686
		 *
687
		 * @see core/apps/files/js/filelist.js
688
		 * @private
689
		 */
690
		_onClickDownloadSelected: function(event) {
691
			var files;
692
			var dir = Gallery.currentAlbum;
693
			if (dir === '') {
694
				dir = '/';
695
			}
696
			files = _.pluck(this.getSelectedFiles(), 'path');
697
			files.forEach(function(file, index, files) {
698
				files[index] = OC.basename(file);
699
			});
700
701
			var downloadFileActionIcon = $('#selectedActionsList').find('.download .icon');
702
703
			// don't allow a second click on the download action
704
			if (downloadFileActionIcon.hasClass('icon-loading-small')) {
705
				event.preventDefault();
706
				return;
707
			}
708
709
			var disableLoadingState = function() {
710
				downloadFileActionIcon.removeClass('icon-loading-small');
711
				downloadFileActionIcon.addClass('icon-download');
712
			};
713
			downloadFileActionIcon.removeClass('icon-download');
714
			downloadFileActionIcon.addClass('icon-loading-small');
715
716
			if(this.getSelectedFiles().length > 1) {
717
				OCA.Files.Files.handleDownload(Gallery.getSelectionDownloadUrl(files, dir), disableLoadingState);
718
			}
719
			else {
720
				var first = OC.basename(this.getSelectedFiles()[0].path);
721
				OCA.Files.Files.handleDownload(Gallery.getSelectionDownloadUrl(first, dir), disableLoadingState);
722
			}
723
			return false;
724
	},
725
726
		/**
727
		 * Returns the file info of the selected files
728
		 *
729
		 * @return array of file names
730
		 *
731
		 * @see core/apps/files/js/filelist.js
732
		 * @private
733
		 */
734
		getSelectedFiles: function() {
735
			return _.values(this._selectedFiles);
736
		},
737
738
		/**
739
		 * Returns the file data from a given file element.
740
		 * @param $el file tr element
741
		 * @return file data
742
		 *
743
		 * @see core/apps/files/js/filelist.js
744
		 * @private
745
		 */
746
		elementToFile: function($el){
747
			$el = $($el);
748
			var data = {
749
				id: parseInt($el.attr('data-id'), 10),
750
			};
751
			var path = $el.attr('data-path');
752
			if (path) {
753
				data.path = path;
754
			}
755
			return data;
756
		},
757
758
		/**
759
		 * Selected/deselects the given file element and updated
760
		 * the internal selection cache.
761
		 *
762
		 * @param {Object} $element single image
763
		 * @param {bool} state true to select, false to deselect
764
		 *
765
		 * @see core/apps/files/js/filelist.js
766
		 * @private
767
		 */
768
		_selectFileEl: function($element, state) {
769
			var $checkbox = $element.find('row-element>.image-label>.selectCheckBox');
770
			var oldData = !!this._selectedFiles[$element.data('id')];
771
			var data;
772
			$checkbox.prop('checked', state);
773
			$element.toggleClass('selected', state);
774
			// already selected ?
775
			if (state === oldData) {
776
				return;
777
			}
778
			data = this.elementToFile($element);
779
			if (state) {
780
				this._selectedFiles[$element.data('id')] = data;
781
				this._selectionSummary.add(data);
782
			}
783
			else {
784
				delete this._selectedFiles[$element.data('id')];
785
				this._selectionSummary.remove(data);
786
			}
787
		},
788
789
		/**
790
		 * Event handler for when clicking on a element's checkbox
791
		 *
792
		 * @see core/apps/files/js/filelist.js
793
		 * @private
794
		 */
795
		_onClickFileCheckbox: function(event) {
796
			var $image = $(event.target).closest('.' + GalleryImage.cssClass);
797
			var state = !$image.hasClass('selected');
798
			this._selectFileEl($image, state);
799
			this._lastChecked = $image;
800
			this.updateSelectionSummary();
801
		},
802
803
		/**
804
		 * Update UI based on the current selection
805
		 *
806
		 * @see core/apps/files/js/filelist.js
807
		 * @private
808
		 */
809
		updateSelectionSummary: function() {
810
			var summary = this._selectionSummary.summary;
811
812
			if (summary.totalFiles === 0 && summary.totalDirs === 0) {
813
				$('#selectedActionsList').addClass('hidden');
814
			}
815
			else {
816
				$('#selectedActionsList').removeClass('hidden');
817
			}
818
		},
819
820
	};
821
822
	Gallery.View = View;
823
})(jQuery, _, OC, t, Gallery);
824